﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;

namespace Crypton
{
    /// <summary>
    /// Crypton service (implementation of ICryptonService)
    /// </summary>
    internal class CryptonService : ICryptonService
    {
        private readonly ILogger _logger;
        private const string EncryptedFileExt = ".encr";

        /// <summary>
        /// Initializes a new instance of the <see cref="CryptonService"/> class.
        /// </summary>
        /// <param name="logger">The logger.</param>
        public CryptonService(ILogger logger)
        {
            _logger = logger;
        }

        #region GenerateCryptoKey

        /// <summary>
        /// Generates the crypto key.
        /// </summary>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyLength">Length of the key.</param>
        /// <param name="key">The key.</param>
        public void GenerateCryptoKey(string algorithmName, int keyLength, string key)
        {
            bool symmetricAlg = isSymmetric(algorithmName);
            genKeyPrepareParams(algorithmName, ref keyLength, ref key);

            if (symmetricAlg)
                generateSymmetricKey(algorithmName, keyLength, key);
            else
                generateAsymmetricKey(algorithmName, keyLength, key);
        }

        /// <summary>
        /// Generates the asymmetric key.
        /// </summary>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyLength">Length of the key.</param>
        /// <param name="key">The key.</param>
        private void generateAsymmetricKey(string algorithmName, int keyLength, string key)
        {
            using (var algorithm = (AsymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.KeySize = keyLength;
                saveAsymmetricKey(key, algorithmName, algorithm, ".pubkey", false);
                saveAsymmetricKey(key, algorithmName, algorithm, ".prikey", true);
                _logger.AddText("Crypto key for {0} asymmetric algorithm is created.", algorithmName);
            }
        }

        /// <summary>
        /// Saves the asymmetric key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="algorithm">The algorithm.</param>
        /// <param name="extension">The extension.</param>
        /// <param name="privateKey">if set to <c>true</c> [private key].</param>
        private static void saveAsymmetricKey(string key, string algorithmName, 
            AsymmetricAlgorithm algorithm, string extension, bool privateKey)
        {
            var fileName = key + extension;
            var keyData = Encoding.UTF8.GetBytes(algorithm.ToXmlString(privateKey));
            keyData = algorithmNameDump(algorithmName).Concat(keyData).ToArray();
            File.WriteAllBytes(fileName, keyData);
        }

        /// <summary>
        /// Creates binary dump of algorithm name.
        /// </summary>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <returns></returns>
        private static byte[] algorithmNameDump(string algorithmName)
        {
            var result = Encoding.UTF8.GetBytes(algorithmName);
            result = BitConverter.GetBytes(result.Length).Concat(result).ToArray();
            return result;
        }

        /// <summary>
        /// Generates the symmetric key.
        /// </summary>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyLength">Length of the key.</param>
        /// <param name="key">The key.</param>
        private void generateSymmetricKey(string algorithmName, int keyLength, string key)
        {
            using (var algorithm = (SymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.KeySize = keyLength;
                algorithm.GenerateIV();
                algorithm.GenerateKey();
                var keyData = algorithmNameDump(algorithmName).Concat(algorithm.IV.Concat(algorithm.Key).ToArray()).ToArray();
                if (string.IsNullOrWhiteSpace(Path.GetExtension(key)))
                    key += ".symkey";
                File.WriteAllBytes(key, keyData);
                _logger.AddText("Crypto key for {0} symmetric algorithm is created." +
                    "Key data was saved into {1} file.", algorithmName, key);
            }
        }

        /// <summary>
        /// Gens the key prepare params.
        /// </summary>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyLength">Length of the key.</param>
        /// <param name="key">The key.</param>
        private static void genKeyPrepareParams(string algorithmName, ref int keyLength, ref string key)
        {
            var algorithm = CryptoConfig.CreateFromName(algorithmName);
            using ((IDisposable)algorithm)
            {
                if (keyLength == 0)
                {
                    KeySizes[] keySizes = null;
                    if (algorithm is SymmetricAlgorithm)
                        keySizes = ((SymmetricAlgorithm)algorithm).LegalKeySizes;
                    if (algorithm is AsymmetricAlgorithm)
                        keySizes = ((AsymmetricAlgorithm)algorithm).LegalKeySizes;
                    keyLength = (from k in keySizes select k.MaxSize).Max();
                }

                if (string.IsNullOrWhiteSpace(key))
                    key = Guid.NewGuid().ToString("N");
            }
        }

        #endregion

        #region Encrypt

        /// <summary>
        /// Encrypts the files.
        /// </summary>
        /// <param name="sourceMask">The source mask.</param>
        /// <param name="key">The key.</param>
        /// <param name="destDir">The dest dir.</param>
        public void EncryptFiles(string sourceMask, string key, string destDir)
        {
            IEnumerable<string> sources;
            string algorithmName;
            byte[] keyInfo;
            destDir = prepareCryptoData(sourceMask, destDir, key, out sources, out algorithmName, out keyInfo);
            sources.ToList().ForEach(fileName => encryptFile(fileName, algorithmName, keyInfo, destDir));
        }

        /// <summary>
        /// Encrypts the file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destDir">The dest dir.</param>
        private void encryptFile(string fileName, string algorithmName, byte[] keyInfo, string destDir)
        {
            if (isSymmetric(algorithmName))
                encryptFileSymmetric(fileName, algorithmName, keyInfo, destDir);
            else
                encryptFileAsymmetric(fileName, algorithmName, keyInfo, destDir);
            _logger.AddText("... {0} encrypted using {1} algorithm", fileName, algorithmName);
        }

        /// <summary>
        /// Encrypts the file asymmetric.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destDir">The dest dir.</param>
        private static void encryptFileAsymmetric(string fileName, string algorithmName, byte[] keyInfo, string destDir)
        {
            using (var algorithm = (AsymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.FromXmlString(Encoding.UTF8.GetString(keyInfo));
                var encryptMethod = algorithm.GetType().GetMethod("Encrypt", new[] {typeof (byte[]), typeof (bool)});
                if (encryptMethod != null)
                {
                    byte[] symKey;
                    using (var symAlg = new RijndaelManaged())
                    {
                        symAlg.GenerateIV();
                        symAlg.GenerateKey();
                        symKey = symAlg.IV.Concat(symAlg.Key).ToArray();
                    }
                    // header is encrypted on assymetric algorithm session key prefixed with it's length
                    var header = (byte[])encryptMethod.Invoke(algorithm, new object[] { symKey, true });
                    header = BitConverter.GetBytes(header.Length).Concat(header).ToArray();
                    encryptFileSymmetric(fileName, "Rijndael", symKey, destDir, header);
                }
                else
                    throw new ApplicationException(string.Format("'Encrypt' method is not found in {0} algorithm", 
                        algorithmName));
            }
        }

        /// <summary>
        /// Encrypts the file symmetric.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destDir">The dest dir.</param>
        /// <param name="header">The header.</param>
        private static void encryptFileSymmetric(string fileName, string algorithmName, 
            IEnumerable<byte> keyInfo, string destDir, byte[] header = null)
        {
            using (var algorithm = (SymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.IV = keyInfo.Take(algorithm.BlockSize / 8).ToArray();
                algorithm.Key = keyInfo.Skip(algorithm.IV.Length).ToArray();
                var destFileName = Path.Combine(destDir, Path.GetFileName(fileName) + EncryptedFileExt);
                using(var destStream = new FileStream(destFileName, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    if(header != null)
                        destStream.Write(header, 0, header.Length);

                    using (var encryptor = new CryptoStream(destStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        var sourceBuffer = File.ReadAllBytes(fileName);
                        encryptor.Write(sourceBuffer, 0, sourceBuffer.Length);
                        encryptor.Close();
                    }
                    destStream.Close();
                }
            }
        }

        #endregion

        #region Decrypt

        /// <summary>
        /// Decrypts the files.
        /// </summary>
        /// <param name="sourceMask">The source mask.</param>
        /// <param name="key">The key.</param>
        /// <param name="destDir">The dest dir.</param>
        public void DecryptFiles(string sourceMask, string key, string destDir)
        {
            IEnumerable<string> sources;
            string algorithmName;
            byte[] keyInfo;
            destDir = prepareCryptoData(sourceMask, destDir, key, out sources, out algorithmName, out keyInfo);

            sources.ToList().ForEach(fileName => decryptFile(fileName, algorithmName, keyInfo, destDir));
        }

        /// <summary>
        /// Decrypts the file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destDir">The dest dir.</param>
        private void decryptFile(string fileName, string algorithmName, byte[] keyInfo, string destDir)
        {
            var destFileName = Path.GetFileName(fileName) ?? "";
            if (destFileName.EndsWith(EncryptedFileExt))
                destFileName = Path.GetFileNameWithoutExtension(destFileName) ?? "";
            destFileName = Path.Combine(destDir, destFileName);

            if (isSymmetric(algorithmName))
                decryptFileSymmetric(fileName, algorithmName, keyInfo, destFileName);
            else
                decryptFileAsymmetric(fileName, algorithmName, keyInfo, destFileName);
            _logger.AddText("... {0} decrypted using {1} algorithm", fileName, algorithmName);
        }

        /// <summary>
        /// Decrypts the file asymmetric.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destFileName">Name of the dest file.</param>
        private static void decryptFileAsymmetric(string fileName, string algorithmName,
            byte[] keyInfo, string destFileName)
        {
            var sourceData = File.ReadAllBytes(fileName);
            var encryptedSessionKeyLen = BitConverter.ToInt32(sourceData, 0);
            var encryptedSessionKey = sourceData.Skip(4).Take(encryptedSessionKeyLen).ToArray();
            using (var algorithm = (AsymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.FromXmlString(Encoding.UTF8.GetString(keyInfo));
                var decryptMethod = algorithm.GetType().GetMethod("Decrypt", new[] { typeof(byte[]), typeof(bool) });
                if (decryptMethod != null)
                {
                    var symKeyInfo = (byte[])decryptMethod.Invoke(algorithm, new object[] { encryptedSessionKey, true });
                    var encryptedData = sourceData.Skip(4 + encryptedSessionKey.Length).ToArray();
                    decryptFileSymmetric(encryptedData, "Rijndael", symKeyInfo, destFileName);
                }
                else
                    throw new ApplicationException(string.Format("'Decrypt' method is not found in {0} algorithm",
                        algorithmName));
            }
        }

        /// <summary>
        /// Decrypts the file symmetric.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destFileName">Name of the dest file.</param>
        private static void decryptFileSymmetric(string fileName, string algorithmName, byte[] keyInfo, string destFileName)
        {
            decryptFileSymmetric(File.ReadAllBytes(fileName), algorithmName, keyInfo, destFileName);
        }

        /// <summary>
        /// Decrypts the file symmetric.
        /// </summary>
        /// <param name="encryptedData">The encrypted data.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destFileName">Name of the dest file.</param>
        private static void decryptFileSymmetric(byte[] encryptedData, string algorithmName, byte[] keyInfo, string destFileName)
        {
            using (var algorithm = (SymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.IV = keyInfo.Take(algorithm.BlockSize / 8).ToArray();
                algorithm.Key = keyInfo.Skip(algorithm.IV.Length).ToArray();
                var sourceStream = new MemoryStream(encryptedData);
                using (var cryptoStream = new CryptoStream(sourceStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read))
                {
                    using (var destStream = new FileStream(destFileName, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        var buffer = new byte[1024*1024];
                        int readCount;
                        while ((readCount = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
                            destStream.Write(buffer, 0, readCount);
                        cryptoStream.Close();
                        destStream.Close();
                    }
                }
            }
        }


        #endregion

        #region Sign

        public void SignFiles(string sourceMask, string privateKey, string destDir)
        {
            IEnumerable<string> sources;
            string algorithmName;
            byte[] keyInfo;
            destDir = prepareCryptoData(sourceMask, destDir, privateKey, out sources, out algorithmName, out keyInfo);
            if(isSymmetric(algorithmName))
                throw new ApplicationException(string.Format("{0} is symmetric algorithm {1} key and shouldn't be used for signing", privateKey, algorithmName));
            sources.ToList().ForEach(fileName => signFile(fileName, algorithmName, keyInfo, destDir, privateKey));
        }

        /// <summary>
        /// Signs the file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <param name="destDir">The dest dir.</param>
        /// <param name="privateKeyFileName">Name of the private key file.</param>
        private void signFile(string fileName, string algorithmName, byte[] keyInfo, 
            string destDir, string privateKeyFileName)
        {
            if(!File.Exists(fileName))
                throw new FileNotFoundException("File not found", fileName);

            using (var algorithm = (AsymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                algorithm.FromXmlString(Encoding.UTF8.GetString(keyInfo));
                // sign details
                var signData = new SignData()
                                   {
                                       ComputerName = Environment.MachineName,
                                       UserName = Environment.UserName,
                                       KeyName = Path.GetFileNameWithoutExtension(privateKeyFileName),
                                       SignedAt = DateTime.Now,
                                       SignedFileName = Path.GetFileName(fileName)
                                   };

                // look for SignData method in algorithm
                var signMethod = algorithm.GetType().GetMethod("SignData", new[] {typeof(byte[])});
                if (signMethod == null)
                    signMethod = algorithm.GetType().GetMethod("SignData", new[] { typeof(byte[]), typeof(object) });

                if (signMethod != null)
                {
                    var callParams = new object[signMethod.GetParameters().Length];
                    var signDataBytes = serializeObject(signData);
                    // file data + sign data is subject of signing
                    callParams[0] = File.ReadAllBytes(fileName).Concat(signDataBytes).ToArray();
                    if(signMethod.GetParameters().Length > 1)
                        callParams[1] = new SHA1CryptoServiceProvider();
                    // evaluate sign
                    var signResult = (byte[]) signMethod.Invoke(algorithm, callParams);
                    // compose sign file data
                    var signFileData = new SignFileData()
                                           {
                                               KeyName = signData.KeyName,
                                               SignData = signDataBytes,
                                               SignBytes = signResult
                                           };
                    File.WriteAllBytes(Path.Combine(destDir, Path.GetFileName(fileName)+".sign"), 
                        serializeObject(signFileData));
                    _logger.AddText("... file {0} is signed", fileName);
                }
                else
                    throw new ApplicationException(string.Format("'Sign' method is not found in {0} algorithm",
                        algorithmName));
            }
        }

        /// <summary>
        /// Serializes the object.
        /// </summary>
        /// <param name="sourceObject">The source object.</param>
        /// <returns></returns>
        private static byte[] serializeObject(object sourceObject)
        {
            var formatter = new BinaryFormatter();
            var serialStream = new MemoryStream();
            formatter.Serialize(serialStream, sourceObject);
            serialStream.Close();
            return serialStream.ToArray();
        }

        #endregion

        #region Verify sign

        /// <summary>
        /// Validates the sign.
        /// </summary>
        /// <param name="sourceMask">The source mask.</param>
        /// <param name="publicKeysDir">The public keys dir.</param>
        /// <param name="verbose"></param>
        public bool VerifySign(string sourceMask, string publicKeysDir, bool verbose)
        {
            // get source files list
            var sourceDir = Path.GetDirectoryName(sourceMask) ?? "";
            if (sourceDir == "") sourceDir = Environment.CurrentDirectory;
            IEnumerable<string> sources;
            if (sourceMask.Contains("*") || sourceMask.Contains("?"))
                sources = Directory.GetFiles(sourceDir, Path.GetFileName(sourceMask) ?? "");
            else
                sources = new[] { sourceMask };

            // evaluate public keys dir
            if (string.IsNullOrWhiteSpace(publicKeysDir) || !Directory.Exists(publicKeysDir))
                publicKeysDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

            var result = true;

            // verify every source file except "*.sign" files
            sources.ToList().Where(fileName => !fileName.EndsWith(".sign")).ToList().ForEach(fileName => 
                result &= verifyFileSign(fileName, publicKeysDir, verbose));

            return result;
        }

        /// <summary>
        /// Verifies the file's sign.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="publicKeysDir">The public keys dir.</param>
        /// <param name="verbose">if set to <c>true</c> [verbose].</param>
        private bool verifyFileSign(string fileName, string publicKeysDir, bool verbose)
        {
            // input parameters check
            if (!File.Exists(fileName))
                throw new FileNotFoundException("File not found", fileName);

            var signFileName = fileName + ".sign";
            if (!File.Exists(signFileName))
                throw new FileNotFoundException("Sign file not found", fileName);

            var fileData = (SignFileData) deserializeObject(File.ReadAllBytes(signFileName));
            var publicKeyFileName = Path.Combine(publicKeysDir, fileData.KeyName + ".pubkey");
            if (!File.Exists(publicKeyFileName))
                throw new FileNotFoundException("Public key file not found", publicKeyFileName);

            // parse key file data
            byte[] keyInfo;
            var algorithmName = parseKey(publicKeyFileName, out keyInfo);

            var isValid = true;

            using (var algorithm = (AsymmetricAlgorithm)CryptoConfig.CreateFromName(algorithmName))
            {
                // load public key data from keyInfo
                algorithm.FromXmlString(Encoding.UTF8.GetString(keyInfo));

                // look for public VerifyData method in algorithm
                var verifyDataMethod = algorithm.GetType().GetMethod("VerifyData", 
                    new[] { typeof(byte[]), typeof(byte[]) });

                if (verifyDataMethod == null)
                    verifyDataMethod = algorithm.GetType().GetMethod("VerifyData", 
                        new[] { typeof(byte[]), typeof(object), typeof(byte[]) });

                if (verifyDataMethod != null)
                {
                    var callParams = new object[verifyDataMethod.GetParameters().Length];
                    // sign subject is file data + sign data (user name, comp name, sign time etc.)
                    callParams[0] = File.ReadAllBytes(fileName).Concat(fileData.SignData).ToArray();
                    if (verifyDataMethod.GetParameters().Length > 2)
                        callParams[1] = new SHA1CryptoServiceProvider();
                    callParams[callParams.Length - 1] = fileData.SignBytes;
                    // verify sign
                    isValid = (bool)verifyDataMethod.Invoke(algorithm, callParams);
                    SignData signData = null;
                    if (isValid)
                    {
                        // additional check for valid signData.KeyName
                        signData = (SignData)deserializeObject(fileData.SignData);
                        isValid = signData.KeyName == fileData.KeyName;
                    }
                    if (isValid)
                    {
                        _logger.AddText("... file {0} sign is valid.", fileName);
                        if (verbose)
                        {
                            // output sign details
                            _logger.AddText("Sign details:", fileName);
                            signData.GetType().GetFields().ToList().ForEach(
                                x => _logger.AddText("   {0} = {1}", x.Name, x.GetValue(signData)));
                        }
                    }
                    else
                        _logger.AddText("... file {0} sign is invalid.", fileName);
                }
                else
                    throw new ApplicationException(string.Format("'VerifyData' method is not found in {0} algorithm",
                        algorithmName));
            }
            return isValid;
        }

        /// <summary>
        /// Deserializes the object.
        /// </summary>
        /// <param name="sourceBytes">The source bytes.</param>
        /// <returns></returns>
        private static object deserializeObject(byte[] sourceBytes)
        {
            var formatter = new BinaryFormatter();
            var serialStream = new MemoryStream(sourceBytes);
            var result = formatter.Deserialize(serialStream);
            serialStream.Close();
            return result;
        }

        #endregion

        /// <summary>
        /// Prepares the crypto data.
        /// </summary>
        /// <param name="sourceMask">The source mask.</param>
        /// <param name="destDir">The dest dir.</param>
        /// <param name="key">The key.</param>
        /// <param name="sources">The sources.</param>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <param name="keyInfo">The key info.</param>
        /// <returns></returns>
        private static string prepareCryptoData(string sourceMask, string destDir, string key, 
            out IEnumerable<string> sources, out string algorithmName, out byte[] keyInfo)
        {
            var sourceDir = Path.GetDirectoryName(sourceMask) ?? "";
            if (sourceDir == "") sourceDir = Environment.CurrentDirectory;
            if (sourceMask.Contains("*") || sourceMask.Contains("?"))
                sources = Directory.GetFiles(sourceDir, Path.GetFileName(sourceMask) ?? "");
            else
                sources = new[] { sourceMask };

            if (string.IsNullOrWhiteSpace(destDir))
                destDir = sourceDir;

            if (!Directory.Exists(destDir))
                throw new ApplicationException(string.Format("Destination directory {0} is not found", destDir));

            if (!File.Exists(key))
                throw new ApplicationException(string.Format("Key file {0} is not found", key));

            algorithmName = parseKey(key, out keyInfo);
            return destDir;
        }

        private static string parseKey(string key, out byte[] keyInfo)
        {
            var keyData = File.ReadAllBytes(key);
            var algNameSize = BitConverter.ToInt32(keyData, 0);
            var algorithmName = Encoding.UTF8.GetString(keyData, 4, algNameSize);
            keyInfo = keyData.Skip(4 + algNameSize).ToArray();
            return algorithmName;
        }

        /// <summary>
        /// Determines whether the specified algorithm name is symmetric.
        /// </summary>
        /// <param name="algorithmName">Name of the algorithm.</param>
        /// <returns>
        ///   <c>true</c> if the specified algorithm name is symmetric; otherwise, <c>false</c>.
        /// </returns>
        private static bool isSymmetric(string algorithmName)
        {
            bool symmetric;
            using (var algorithm = (IDisposable)CryptoConfig.CreateFromName(algorithmName))
            {
                if (!(algorithm is SymmetricAlgorithm) && !(algorithm is AsymmetricAlgorithm))
                    throw new ApplicationException("{0} is neither SymmetricAlgorithm nor AsymmetricAlgorithm descendant");
                symmetric = algorithm is SymmetricAlgorithm;
            }
            return symmetric;
        }

    }
}
